﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows.Forms;
using PHPDecoder;
using System.Text.RegularExpressions;

namespace CodeVisual
{
    /// <summary>
    /// Zoznam potencialne podporovanych jazykov v zdrojovych suboroch
    /// </summary>
    public enum ProjectType
    {
        PHP, C_SHARP, CPP, JAVA, PASCAL
    }

    /// <summary>
    /// Vynimky pre chybny projektovy subor
    /// </summary>
    [Serializable]
    class InvalidProjectException:Exception
    {
        readonly string _Message;

        public override string Message
        {
            get
            {
                return this._Message;
            }
        }

        public InvalidProjectException()
        {
            this._Message = "Invalid Project file format";
        }

        public InvalidProjectException(string message)
        {
            _Message = message;
        }
    }


    #region Reprezencia suborov


    [Serializable]
    enum FileNodeType { DIR_TP, FILE_TP };

    [Serializable]
    class myNode
    {
        public FileNodeType tp;
        public string name;
    }

    /// <summary>
    /// Vrchol reprezentujuci subor
    /// </summary>
    [Serializable]
    class FileNode : myNode
    {
        public bool startup = false;
        public string path = null;

        public FileNode()
        {
            tp = FileNodeType.FILE_TP;
        }

        public FileNode(string s)
        {
            tp = FileNodeType.FILE_TP;
            this.name = s;
        }
    }

    /// <summary>
    /// Vrchol reprezentujuci adresar
    /// </summary>
    [Serializable]
    class DirNode : myNode
    {
        public Dictionary<string, myNode> subtree = null;

        public DirNode()
        {
            subtree = new Dictionary<string, myNode>();
            tp = FileNodeType.DIR_TP;
        }

        public DirNode(string s)
        {
            subtree = new Dictionary<string, myNode>();
            tp = FileNodeType.DIR_TP;
            this.name = s;
        }
    }

    #endregion

    /// <summary>
    /// Vsetky informacie o projektoch
    /// nazov, typ projektu, suborove struktury atd.
    /// </summary>
    [Serializable]
    class ProjectInfo
    {
        #region Premenne
        /// <summary>
        /// Cesta k *.cvp (CodeVisual Project) subor
        /// </summary>
        public static string path = null;

        /// <summary>
        /// Subory, ktore su v projekte
        /// </summary>
        public static List<FileNode> files = null;

        /// <summary>
        /// Nazov projektu
        /// </summary>
        public static string name = null;

        /// <summary>
        /// Typ zdrojovych suborov
        /// </summary>
        public static ProjectType tp;

        /// <summary>
        /// Je analyzovana ?
        /// </summary>
        public static bool Analyzed = false;

        /// <summary>
        /// Stream pre citanie projektoveho suboru (*.cvp)
        /// </summary>
        private static StreamReader sr = null;

        /// <summary>
        /// Stream pre zapis projektoveho suboru (*.cvp)
        /// </summary>
        private static StreamWriter sw = null;

        /// <summary>
        /// Adresarova struktura projektu
        /// </summary>
        public static DirNode rootDir = null;

        /// <summary>
        /// Indikuje, ci bola vykonana nejaka zmena
        /// </summary>
        public static bool modified = false;

        /// <summary>
        /// Vysledok analyzy.
        /// </summary>
        public static AnalyseResult result = null;

        #endregion

        private ProjectInfo()
        {
        }

        public static void InitProject(string Name, ProjectType type)
        {
            tp = type;
            name = Name;
            rootDir = new DirNode(name);
        }
        /// <summary>
        /// Analyza projektoveho suboru *.cvp
        /// </summary>
        public static bool AnalyseProjectFile()
        {
            try
            {
                sr = new StreamReader(path);
                int x = 0;

                // 1.byte, typ projektu
                x = sr.Read();
                switch (x)
                {
                    case '0': tp = ProjectType.PHP;
                        break;
                    case '1': tp = ProjectType.C_SHARP;
                        break;
                    default: throw new InvalidProjectException();
                }

                // 2.byte, je analyzovana ?
                x = sr.Read();
                if ((char)x == '1')
                {
                    Analyzed = true;
                }
                else
                {
                    if ((char)x == '0')
                    {
                        Analyzed = false;
                    }
                    else
                        throw new InvalidProjectException();
                }

                // 3.byte, dlzka nazvu projektu
                x = sr.Read();

                // Nazov projektu

                name = ReadBlock(x);

                rootDir = new DirNode(name);

                // Pocet suboru>
                int n = 256 * sr.Read() + sr.Read();

                // subory
                ReadFiles(n, rootDir);

            }
            //catch (InvalidProjectException)
            //{
            //    return false;
            //}
            catch (Exception)
            {
                return false;
            }
            finally
            {
                if (sr != null)
                {
                    sr.Close();
                    sr.Dispose();
                }
            }

            return true;
        }

        /// <summary>
        /// Nacitanie zoznam suborov a priecinkov z cvp suboru
        /// </summary>
        /// <param name="x">Pocet suborov</param>
        /// <param name="node">Aktualny priecinok</param>
        private static void ReadFiles(int x, myNode node)
        {
            for (int i = 0; i < x; i++)
            {
                int c = sr.Read();
                if ((char)c == '0')
                {
                    //Directory
                    c = sr.Read();
                    string dirName = ReadBlock(c);
                    myNode nn = new DirNode(dirName);
                    ((DirNode)node).subtree.Add(dirName, nn);

                    int x1 = sr.Read();
                    int x2 = sr.Read();
                    int n = 256 * x1 + x2;
                    ReadFiles(n, nn);
                }
                else
                    if ((char)c == '1')
                    {
                        c = sr.Read();
                        string fileName = ReadBlock(c);
                        myNode nn = new FileNode(fileName);
                        ((DirNode)node).subtree.Add(fileName, nn);
                        int pathLen = 256*sr.Read()+sr.Read();
                        ((FileNode)nn).path = ReadBlock(pathLen);

                        c = sr.Read();
                        if (c == 1) ((FileNode)nn).startup = true;
                        // File

                    }
            }

        }

        private static void ReadAnalyzedResult()
        {
            int x = 256 * sr.Read() + sr.Read();
        }

        public static string getAbsolutePath(string p)
        {
            Regex r = new Regex("[A-Z]:.*");
            if (r.IsMatch(p)) 
                return p;
            return Path.GetFullPath(Path.GetDirectoryName(path) + Path.DirectorySeparatorChar + p);

        }
        /// <summary>
        /// Nacitanie bloky retazcov zo suboru
        /// </summary>
        /// <param name="x">Pocet bajtov</param>
        /// <returns></returns>
        private static String ReadBlock(int x)
        {
            if (x == 0) return "";

            StringBuilder sb = new StringBuilder();


            char[] buf = new char[x];

            sr.Read(buf, 0, x);
            foreach (char c in buf)
            {
                sb.Append(c);
            }

            return sb.ToString();
        }


        /// <summary>
        /// Ukladanie projektoveho suboru *.cvp
        /// </summary>
        public static bool SaveProject()
        {
            bool Res = false;

            if (path == null)
                Res = SaveAs();
            else
                Res = Save();

            return Res;
        }

        public static bool SaveAs()
        {
            if (rootDir == null)
            {
                MessageBox.Show("Project is not created!");
                return false;
            }
            SaveFileDialog sfd = new SaveFileDialog();
            sfd.Filter = "CodeVisual Project (.cvp) | *.cvp";
            if (sfd.ShowDialog() == DialogResult.OK)
            {
                path = sfd.FileName;
                return Save();
            }

            return false;
        }

        private static bool Save()
        {
            if (path == null) return false;

            if (!WriteToProjectFile()) return false;

            return true;
        }

        private static bool WriteToProjectFile()
        {
            try
            {
                sw = new StreamWriter(path);

                // Typ projektu
                switch (tp)
                {
                    case ProjectType.PHP:
                        sw.Write('0');
                        break;
                    case ProjectType.C_SHARP:
                        sw.Write('1');
                        break;
                    case ProjectType.CPP:
                        sw.Write('2');
                        break;
                    case ProjectType.JAVA:
                        sw.Write('3');
                        break;
                    case ProjectType.PASCAL:
                        sw.Write('4');
                        break;
                    default:
                        return false;
                }

                if (Analyzed) 
                    sw.Write('1');
                else 
                    sw.Write('0');

                WriteString(name, 1);

                // Pocet suborov
                int x = rootDir.subtree.Count;
                sw.Write((char)(x / 256));
                sw.Write((char)(x % 256));

                WriteFiles(rootDir.subtree);

                if (Analyzed)
                {
                }
            }
            catch (Exception ex)
            {
                (new ErrorBox(100, 300, ex.Message)).ShowDialog();
                return false;
            }
            finally
            {
                if (sw != null)
                {
                    MessageBox.Show("Project Saved !");
                    modified = false;
                    sw.Close();
                    sw.Dispose();
                }
            }

            return true;
        }

        private static void WriteString(String s, int x)
        {
            
            int len = s.Length;
            if (x == 1)
            {
                char c = (char)len;
                sw.Write(c);
                sw.Write(s);
            }
            else
            {
                if (x == 2)
                {
                    char c = (char)(len / 256);
                    sw.Write(c);
                    c = (char)(len % 256);
                    sw.Write(c);
                    sw.Write(s);
                }
            }
        }

        private static void WriteFiles(Dictionary<string, myNode> node)
        {
            foreach (KeyValuePair<string,myNode> n in node)
            {
                if (n.Value.tp == FileNodeType.DIR_TP)
                {
                    sw.Write('0');
                    WriteString(n.Value.name, 1);
                    Dictionary<string, myNode> dn = ((DirNode)(n.Value)).subtree;
                    sw.Write((char)(dn.Values.Count / 256));
                    sw.Write((char)(dn.Values.Count % 256));
                    WriteFiles(dn);
                }
                else
                {
                    if (n.Value.tp == FileNodeType.FILE_TP)
                    {
                        sw.Write('1');
                        WriteString(n.Key, 1);
                        string relativePath = GetRelativePath(((FileNode)n.Value).path, path);

                        WriteString(relativePath, 2);
                        if (((FileNode)(n.Value)).startup) sw.Write((char)1);
                        else sw.Write((char)0);
                    }
                }
            }
        }

        public static void reset()
        {
            // Ak je upraveny projekt, tak upozorni uzivatela na ukladanie
            if (modified)
            {
                DialogResult res = MessageBox.Show("The project was modified, Save it ?", "Save", MessageBoxButtons.YesNoCancel);
                if (res == DialogResult.Yes) SaveProject();
            }

            path = null;
            files = null;
            name = null;
            Analyzed = false;
            sr = null;
            sw = null;
            rootDir = null;
            modified = false;
            GC.Collect();
        }
        public static myNode GetNode(string path)
        {
            return FindNode(path, false);
        }
  
        public static void AddNode(string path, string name, FileNodeType ftp, string filePath = null)
        {
            DirNode n = (DirNode)FindNode(path);
            try
            {
                if (n != null)
                {
                    if (ftp == FileNodeType.DIR_TP)
                        n.subtree.Add(name, new DirNode(name));
                    else
                    {
                        FileNode nn = new FileNode(name);
                        nn.path = filePath;
                        n.subtree.Add(name, nn);
                    }
                }
            }
            catch (Exception)
            {
            }
        }
        
        public static void RemoveNode(string path)
        {
            myNode n = FindNode(path, false);
            n = null;
        }

        private static myNode FindNode(string path, bool dir = true)
        {
            string[] str = path.Split(new char[] { '\\' });
            myNode n = rootDir;

            try
            {
                for (int i = 1; i < str.Length; i++)
                {
                    string key = str[i];
                    myNode nn = ((DirNode)n).subtree[key];
                    if (nn.tp == FileNodeType.DIR_TP)
                    {
                        n = nn;
                    }
                    else
                    {
                        if (dir == true)
                            return (DirNode)n;
                        else n = nn;
                    }
                }
            }
            catch (Exception)
            {
                return null;
            }

            return n;
        }

        /// <summary>
        /// Zistenie relativnej cesty subor 1 vzhladom k suboru 2
        /// </summary>
        /// <param name="p1"></param>
        /// <param name="p2"></param>
        /// <returns></returns>
        private static string GetRelativePath(string p1, string p2)
        {
            int x = GetLongestPath(p1, p2);
            if (x == 0) return p1;
            int y = CharCount(p2.Substring(x), '\\');

            string rp = null;
            for (int i = 0; i < y; i++)
            {
                rp += "..\\";
            }

            rp += p1.Substring(x);

            return rp;
        }

        /// <summary>
        /// Zistenie absolutnej cesty pomocou relativnej a priecinok projektoveho suboru
        /// </summary>
        /// <param name="relativePath"></param>
        /// <param name="d"></param>
        /// <returns></returns>
         private static string GetAbsolutePath(string relativePath, string d)
        {
            string path = null;
            path = d.Substring(0, d.LastIndexOf('\\'));
            int ii = relativePath.LastIndexOf("..\\");
            string r = relativePath.Substring(0, ii + 3);
            int c = CharCount(r, '\\');
            int last = d.Length - 1;
            int x = 0;
            for (int i = 0; i <= c; i++)
            {
                x = d.LastIndexOf('\\',last);
                last = x - 1;
            }
            return d.Substring(0, x) +'\\'+ relativePath.Substring(ii + 3);
        }

        /// <summary>
        /// Pocet znakov v retazci
        /// </summary>
        /// <param name="s">retazec</param>
        /// <param name="c">hladany znak</param>
        /// <returns></returns>
        private static int CharCount(string s, char c)
        {
            int x = 0;
            for (int i = 0; i < s.Length; i++)
            {
                if (s[i] == c) x++;
            }
            return x;
        }

        /// <summary>
        /// Zisti najvyssi index najdlhsej spolocnej cesty.
        /// </summary>
        /// <param name="s1">cesta 1</param>
        /// <param name="s2">cesta 2</param>
        /// <returns></returns>
        private static int GetLongestPath(string s1, string s2)
        {
            if (s1 == "" || s2 == "") return 0;
            int i = 0;
            int last = s1.Length > s2.Length ? s1.Length : s2.Length;

            while (i < last)
            {
                if (s1[i] != s2[i]) return i;
                i++;
            }
            return i;
        }

        /// <summary>
        /// Z cesty vyberie nazov suboru
        /// </summary>
        /// <param name="path">Cesta k suboru</param>
        /// <returns></returns>
        private static string getFileName(string path)
        {
            if (path == null || path == "") return null;
            string[] s = path.Split('\\');
            return s[s.Length - 1];
        }
    }
}
